TP N°3 - comprendre le fonctionnement des clauses SQL
Table "Products"
Correspond à la requête : SELECT * FROM ProductsproductId | productLabel | productPrice | productCategory |
---|---|---|---|
1 | Ordinateur Asus | 750 | 1 |
2 | Ordinateur HP | 800 | 1 |
3 | Téléphone HTC | 250 | 2 |
4 | Ordinateur Lenovo | 675 | 1 |
5 | Téléphone iPhone | 5000 | 2 |
6 | Ordinateur MacOS | 10000 | 1 |
7 | Ordinateur Acer | 2 | 1 |
Table "Categories"
Correspond à la requête : SELECT * FROM CategoriescategoryId | categoryLabel |
---|---|
1 | Ordinateur |
2 | Téléphone |
Clause SELECT
Correspond à la requête : SELECT productLabel, productPrice FROM ProductsproductLabel | productPrice |
---|---|
Ordinateur Asus | 750 |
Ordinateur HP | 800 |
Téléphone HTC | 250 |
Ordinateur Lenovo | 675 |
Téléphone iPhone | 5000 |
Ordinateur MacOS | 10000 |
Ordinateur Acer | 2 |
Clause WHERE
Correspond à la requête : SELECT * FROM Products WHERE productPrice > 900productId | productLabel | productPrice | productCategory |
---|---|---|---|
5 | Téléphone iPhone | 5000 | 2 |
6 | Ordinateur MacOS | 10000 | 1 |
Clauses SELECT + WHERE
Correspond à la requête : SELECT productLabel, productPrice FROM Products WHERE productPrice > 900productLabel | productPrice |
---|---|
Téléphone iPhone | 5000 |
Ordinateur MacOS | 10000 |
Clause FROM et produit cartésien
Correspond à la requête : SELECT * FROM Products, CategoriesproductId | productLabel | productPrice | productCategory | categoryId | categoryLabel |
---|---|---|---|---|---|
1 | Ordinateur Asus | 750 | 1 | 1 | Ordinateur |
1 | Ordinateur Asus | 750 | 1 | 2 | Téléphone |
2 | Ordinateur HP | 800 | 1 | 1 | Ordinateur |
2 | Ordinateur HP | 800 | 1 | 2 | Téléphone |
3 | Téléphone HTC | 250 | 2 | 1 | Ordinateur |
3 | Téléphone HTC | 250 | 2 | 2 | Téléphone |
4 | Ordinateur Lenovo | 675 | 1 | 1 | Ordinateur |
4 | Ordinateur Lenovo | 675 | 1 | 2 | Téléphone |
5 | Téléphone iPhone | 5000 | 2 | 1 | Ordinateur |
5 | Téléphone iPhone | 5000 | 2 | 2 | Téléphone |
6 | Ordinateur MacOS | 10000 | 1 | 1 | Ordinateur |
6 | Ordinateur MacOS | 10000 | 1 | 2 | Téléphone |
7 | Ordinateur Acer | 2 | 1 | 1 | Ordinateur |
7 | Ordinateur Acer | 2 | 1 | 2 | Téléphone |
Jointure classique INNER JOIN
Correspond à la requête : SELECT * FROM Products INNER JOIN Cateogies ON productCategory = categoryIdproductId | productLabel | productPrice | productCategory | categoryId | categoryLabel |
---|---|---|---|---|---|
1 | Ordinateur Asus | 750 | 1 | 1 | Ordinateur |
2 | Ordinateur HP | 800 | 1 | 1 | Ordinateur |
3 | Téléphone HTC | 250 | 2 | 2 | Téléphone |
4 | Ordinateur Lenovo | 675 | 1 | 1 | Ordinateur |
5 | Téléphone iPhone | 5000 | 2 | 2 | Téléphone |
6 | Ordinateur MacOS | 10000 | 1 | 1 | Ordinateur |
7 | Ordinateur Acer | 2 | 1 | 1 | Ordinateur |
Clause ORDER BY (Plus compliqué)
Correspond à la requête : SELECT * FROM Products ORDER BY productPrice DESCproductId | productLabel | productPrice | productCategory |
---|---|---|---|
6 | Ordinateur MacOS | 10000 | 1 |
5 | Téléphone iPhone | 5000 | 2 |
2 | Ordinateur HP | 800 | 1 |
1 | Ordinateur Asus | 750 | 1 |
4 | Ordinateur Lenovo | 675 | 1 |
3 | Téléphone HTC | 250 | 2 |
7 | Ordinateur Acer | 2 | 1 |
Clause GROUP BY (Plus compliqué)
Correspond à la requête (fictive): SELECT * FROM Products INNER JOIN Cateogies ON productCategory = categoryId GROUP BY categoryIdN.B.: on comprend bien qu'il ne s'agisse pas d'une requête "exécutable" dans la mesure où le résultat produit est un tableau à 3 dimensions ! En effet, en utilisant la clause GROUP BY, on a constitué un tableau de "groupes", à savoir un tableau de tableaux à 2 dimensions. Et maintenant que les groupes sont constitués, il faut "agréger" tout cela de sorte qu'à la fin il ne nous reste plus qu'une ligne par groupe.
productId | productLabel | productPrice | productCategory | categoryId | categoryLabel |
---|---|---|---|---|---|
6 7 1 4 2 | Ordinateur MacOS Ordinateur Acer Ordinateur Asus Ordinateur Lenovo Ordinateur HP | 10000 2 750 675 800 | 1 1 1 1 1 | 1 | Ordinateur Ordinateur Ordinateur Ordinateur Ordinateur |
3 5 | Téléphone HTC Téléphone iPhone | 250 5000 | 2 2 | 2 | Téléphone Téléphone |
Correspond à la requête (fictive): SELECT categoryLabel, COUNT(productId) FROM Products INNER JOIN Cateogies ON productCategory = categoryId GROUP BY categoryId
N.B.: on comprend bien que, cette fois encore, la requêtes n'est pas "exécutable" puisqu'elle retourne encore une fois un tableau à 3 dimensions !
COUNT(productId) | categoryLabel |
---|---|
5 | Ordinateur Ordinateur Ordinateur Ordinateur Ordinateur |
2 | Téléphone Téléphone |
Correspond à la requête: SELECT categoryLabel, COUNT(productId) FROM Products INNER JOIN Cateogies ON productCategory = categoryId GROUP BY categoryId, categoryLabel
N.B.: on comprend bien que, cette fois-ci, le résultat produit étant bien à 2 dimensions, correspond bien à une requete "exécutable" !
COUNT(productId) | categoryLabel |
---|---|
5 | Ordinateur |
2 | Téléphone |
Conséquence sur l'ordre d'exécution des clauses
Une fois ces algorithmes codés et/ou ces résultats compris, on comprendra sans doute mieux l'ordre d'exécution des clauses SQL. Quoiqu'il en soit, on retiendra :- que nécessairement, la clause SELECT (la projection) n'est exécutée qu'à la fin ;
- que les agrégats (COUNT, SUM, AVG, etc.) sont calculés sur la base des regroupements créés à l'issue d'un GROUP BY ;
- qu'une requête contenant une clause GROUP BY ne pourra nécessaiement pas fonctionner si elle retourne un résultat à 3 dimensions ;
- que la clause WHERE (selection, i.e. restriction) est toujours exécutée avant le GROUP BY. Dès lors, inutile de chercher à insérer des agrégats dans le WHERE, ça ne fonctionnera pas, les regroupements n'étant pas encore effectués ;
- que la clause HAVING (selection, i.e. restriction) n'a qu'une chose de plus qu'une WHERE => elle est executée après exécution d'un GROUP BY. Et c'est bien là son intérêt ;
- qu'en présence d'un GROUP BY, les champs "sélectionnés" (projetés) hors agrégats doivent très normalement figurés dans le GROUP BY (afin d'éviter un résultat invalide à 3 dimensions) ;
- qu'une sous-requête produit complètement similaire à une table. Et c'est bien pour cela qu'on peut utiliser une sous-requête en outre dans la clause FROM.
Clause INSERT INTO
Correspond à la requête : INSERT INTO Products VALUES (8, "Tablette Microsoft", 500.00, null), équivalente à la requête INSERT INTO Products(productId, productLabel, productPrice, productCategory) VALUES (8, "Tablette Microsoft", 500.00, null)N.B.: on comprend bien que, si l'on ne précise pas les champs, l'ordre des valeurs est capital ! En effet, le SGBD ne saurait jouer aux devinettes et ne fera pas d'hypothèse. Les champs de la table ont été créés dans un certain ordre. Et c'est dans cet ordre que le SGBD s'attend à recevoir les valeurs.
productId | productLabel | productPrice | productCategory |
---|---|---|---|
1 | Ordinateur Asus | 750 | 1 |
2 | Ordinateur HP | 800 | 1 |
3 | Téléphone HTC | 250 | 2 |
4 | Ordinateur Lenovo | 675 | 1 |
5 | Téléphone iPhone | 5000 | 2 |
6 | Ordinateur MacOS | 10000 | 1 |
7 | Ordinateur Acer | 2 | 1 |
8 | Tablette Microsoft | 500 |
Clauses DELETE FROM ... WHERE ...
Correspond à la requête : DELETE FROM Products WHERE productCategory = 2N.B.:
- bien entendu, lors d'un DELETE FROM, le WHERE consiste en une restriction et, en cela, est absolumment similaire au WHERE. Cependant, et vous le savez, il ne s'agit plus de conserver mais de supprimer les lignes pour lesquelles des conditions sont vérifiées ;
- surtout, on constate qu'il s'agit d'une WHERE habituel, et plus généralement d'une clause comme une autre. Aussi, rien ne nous interdit d'y placer une sous-requête.
productId | productLabel | productPrice | productCategory |
---|---|---|---|
1 | Ordinateur Asus | 750 | 1 |
2 | Ordinateur HP | 800 | 1 |
4 | Ordinateur Lenovo | 675 | 1 |
6 | Ordinateur MacOS | 10000 | 1 |
7 | Ordinateur Acer | 2 | 1 |
8 | Tablette Microsoft | 500 |
Clauses LEFT et RIGHT JOIN
Correspond à la requête : SELECT * FROM Products P LEFT JOIN Categories C ON P.productCategory = C.categoryIdN.B.: on retiendra bien que l'intérêt d'un LEFT JOIN, c'est de pouvoir conserver toutes les lignes de la table de gauche ! Et pas forcément en un seul exemplaire d'ailleurs...
productId | productLabel | productPrice | productCategory | categoryId | categoryLabel |
---|---|---|---|---|---|
1 | Ordinateur Asus | 750 | 1 | 1 | Ordinateur |
2 | Ordinateur HP | 800 | 1 | 1 | Ordinateur |
4 | Ordinateur Lenovo | 675 | 1 | 1 | Ordinateur |
6 | Ordinateur MacOS | 10000 | 1 | 1 | Ordinateur |
7 | Ordinateur Acer | 2 | 1 | 1 | Ordinateur |
8 | Tablette Microsoft | 500 |
Strictement équivalent à la requête précédente : SELECT * FROM Categories C RIGHT JOIN Products P ON C.categoryId = P.productCategory
N.B.: RIGHT JOIN, LEFT JOIN... Finalement, c'est la même histoire. Tout LEFT JOIN peut être transformé en RIGHT JOIN et inversement. Le LEFT JOIN conserve les lignes de la table de gauche, le RIGHT JOIN celles de droites...
productId | productLabel | productPrice | productCategory | categoryId | categoryLabel |
---|---|---|---|---|---|
1 | Ordinateur Asus | 750 | 1 | 1 | Ordinateur |
2 | Ordinateur HP | 800 | 1 | 1 | Ordinateur |
4 | Ordinateur Lenovo | 675 | 1 | 1 | Ordinateur |
6 | Ordinateur MacOS | 10000 | 1 | 1 | Ordinateur |
7 | Ordinateur Acer | 2 | 1 | 1 | Ordinateur |
8 | Tablette Microsoft | 500 |
Autre remarques
On retiendra également :- que le résultat d'une requête SELECT est similaire à une table à ceci près que la requête est calculée !
- qu'il est dès lors assez naturel qu'une vue (CREATE/ALTER VIEW) se comporte comme une table. Après tout, une vue n'est rien d'autre qu'une requête nommée et stockée, mais recalculée à chaque fois... ;
- qu'il est encore assez naturel qu'on puisse utiliser des sous-requêtes dans une clause FROM. Après tout, un FROM attend des tableaux à 2 dimensions. Alors, qu'importe qu'il s'agisse de tables ou de requêtes SELECT.
Conclusion
Tout d'abord, j'espère que ce travail vous a intéressé et a été instructif. J'espère encore que votre travail et l'étude de la correction vous permettra et/ou vous a permis :- de vous casser les méninges. Et il est bien naturel que vous ayez rencontré des difficultés ;
- de lever l'ambiguïté sur le fonctionnement des clauses SQL ;
- de comprendre un peu mieux la face cachée des SGBD.
Ensuite, je vous suggère de relire ces résultats à tête reposée de sorte que les clauses SQL n'aient plus de secret pour vous !
Bien entendu, on comprendra qu'il ne s'agit pas d'une version optimisée (performances) et que l'intérêt de ce TP/projet était bel et bien d'en apprendre plus sur le fonctionnement du SQL et sur le fonctionnement interne d'un SGBD.
Merci à tous !